{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "453a8807-2e81-4164-b310-fd65028f4f1a",
   "metadata": {},
   "source": [
    "# GenAI Usage of UC functions as Agent Tools\n",
    "\n",
    "In this tutorial, we will be walking through the process of how to get started with using [Unity Catalog](https://www.unitycatalog.io/)'s function storage capabilities to serve as a GenAI Agent tool repository. \n",
    "\n",
    "**Prerequisites for this tutorial**:\n",
    "\n",
    "1. A clone of the [Unity Catalog repository](https://github.com/unitycatalog/unitycatalog).\n",
    "\n",
    "    ```sh\n",
    "    git clone https://github.com/unitycatalog/unitycatalog\n",
    "    ```\n",
    "\n",
    "2. JDK-17 installed on your system (in order to build and run the Unity Catalog services)<sup>1</sup>\n",
    "3. A Python installation version >= 3.9\n",
    "4. [Docker Desktop](https://www.docker.com/products/docker-desktop/) (recommended; you can also install the docker engine yourself)\n",
    "\n",
    "<sup>1</sup> (New to managing Java environments? [jenv](https://github.com/jenv/jenv) is a fantastic tool that can help!) \n",
    "\n",
    "**Package Requirements (Python)**:\n",
    "\n",
    "You can install the required packages for this tutorial by running:\n",
    "\n",
    "```shell\n",
    "pip install unitycatalog-client unitycatalog-ai unitycatalog-openai\n",
    "```\n",
    "\n",
    "## Objectives\n",
    "\n",
    "By the end of this tutorial you will have learned:\n",
    "\n",
    "- How to start a Unity Catalog server using Docker\n",
    "- How to use the `unitycatalog-client` to create a Catalog and a Schema to store your functions\n",
    "- How to create Unity Catalog Functions to store Python callables\n",
    "- How to list functions available in a given Catalog and Schema using the `unitycatalog-client` package\n",
    "- How to execute stored functions for validation\n",
    "- How to create a UCFunctionToolkit instance to register functions as tools for use in calls to OpenAI\n",
    "- How to build a very simple tool calling Agent using OpenAI and Unity Catalog\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bafb35c7-ca83-4c73-96db-ccfdeb094cfa",
   "metadata": {},
   "source": [
    "## Starting a UC server\n",
    "\n",
    "After you've cloned the Unity Catalog repository, navigate to the repository root (`unitycatalog`). Once there, simply run:\n",
    "\n",
    "```shell\n",
    "docker compose up\n",
    "```\n",
    "\n",
    "The Unity Catalog services will commence fetching, compiling, and building the server and UI infrastructure. Once complete, you will see in a terminal window:\n",
    "\n",
    "```text\n",
    "unitycatalog-server-1  | ###################################################################\n",
    "unitycatalog-server-1  | #  _    _       _ _            _____      _        _              #\n",
    "unitycatalog-server-1  | # | |  | |     (_) |          / ____|    | |      | |             #\n",
    "unitycatalog-server-1  | # | |  | |_ __  _| |_ _   _  | |     __ _| |_ __ _| | ___   __ _  #\n",
    "unitycatalog-server-1  | # | |  | | '_ \\| | __| | | | | |    / _` | __/ _` | |/ _ \\ / _` | #\n",
    "unitycatalog-server-1  | # | |__| | | | | | |_| |_| | | |___| (_| | || (_| | | (_) | (_| | #\n",
    "unitycatalog-server-1  | #  \\____/|_| |_|_|\\__|\\__, |  \\_____\\__,_|\\__\\__,_|_|\\___/ \\__, | #\n",
    "unitycatalog-server-1  | #                      __/ |                                __/ | #\n",
    "unitycatalog-server-1  | #                     |___/               v0.3.0-SNAPSHOT  |___/  #\n",
    "unitycatalog-server-1  | ###################################################################\n",
    "unitycatalog-server-1  |\n",
    "```\n",
    "\n",
    "At this point, you can interface with the server as shown in this tutorial."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "755f060a-2717-4919-9e95-f661f5157b87",
   "metadata": {},
   "source": [
    "## Configuration\n",
    "\n",
    "This next cell sets up access to your running UnityCatalog server, creates a Catalog and a Schema that we will be using throughout the remainder of this tutorial. \n",
    "\n",
    "> Note: The unitycatalog-client is an aiohttp-based package. When directly interfacing with the APIs in that package, make sure to use async interfaces when making calls. The `UnitycatalogFunctionClient` API offers synchronous (shown below) convenience methods for creating catalogs and schemas, though."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2a29fc72-8346-431b-904b-82910523b72b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "SchemaInfo(name='AISchema', catalog_name='AICatalog', comment='This is a schema used for storing GenAI functions.', properties={}, full_name='AICatalog.AISchema', owner=None, created_at=1732297125510, created_by=None, updated_at=1732297125510, updated_by=None, schema_id='4925f53c-216a-44e7-9185-34c39fb9f51f')"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from unitycatalog.ai.core.client import UnitycatalogFunctionClient\n",
    "from unitycatalog.client import ApiClient, Configuration\n",
    "\n",
    "CATALOG = \"AICatalogDemonstration\"\n",
    "SCHEMA = \"AISchemaDemonstration\"\n",
    "\n",
    "config = Configuration(host=\"http://localhost:8080/api/2.1/unity-catalog\")\n",
    "client = ApiClient(configuration=config)\n",
    "\n",
    "uc_client = UnitycatalogFunctionClient(api_client=client)\n",
    "\n",
    "uc_client.uc.create_catalog(\n",
    "    name=CATALOG, comment=\"A demonstration catalog for the AI functionality in Unity Catalog.\"\n",
    ")\n",
    "uc_client.uc.create_schema(\n",
    "    name=SCHEMA,\n",
    "    catalog_name=CATALOG,\n",
    "    comment=\"A demonstration schema for holding tutorial Python functions for GenAI usage.\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87c8e3e6-2e45-4a67-8dd7-67f05a3439e5",
   "metadata": {},
   "source": [
    "## Create a Test function\n",
    "\n",
    "Once we have our Catalog `\"AICatalog\"` and our Schema `\"AISchema\"` created, we can now register a test function to UnityCatalog. \n",
    "\n",
    "This simple test function is defined as any other Python function, with a few caveats that are required to be met in order for the function to be used as a GenAI tool. You must:\n",
    "\n",
    "- **Define types**: type hints are **required** for all parameters and for the return type of the function.\n",
    "- **Use a Docstring**: A Google-style Docstring is **required** for the `comment` block within UnityCatalog's function APIs to be populated and for the parameter description comments to be populated. Without a `comment`, your GenAI LLM will have no idea what the function is for or how to use it.\n",
    "- **Local imports**: If you are using a library that is not defined within the core Python libraries, you should include your import within the function body.\n",
    "\n",
    "> Note: If you would like to replace an existing function of the same name (your function name will be the name of your Python callable - in the case below, `\"my_test_func\"`), set `replace=True` to overwrite it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e30b66a8-3157-431a-8b5a-5795ec2d79d5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "FunctionInfo(name='my_test_func', catalog_name='AICatalog', schema_name='AISchema', input_params=FunctionParameterInfos(parameters=[FunctionParameterInfo(name='a', type_text='STRING', type_json='{\"name\": \"a\", \"type\": \"string\", \"nullable\": false, \"metadata\": {\"comment\": \"the first string\"}}', type_name=<ColumnTypeName.STRING: 'STRING'>, type_precision=None, type_scale=None, type_interval_type=None, position=0, parameter_mode=None, parameter_type=None, parameter_default=None, comment='the first string'), FunctionParameterInfo(name='b', type_text='STRING', type_json='{\"name\": \"b\", \"type\": \"string\", \"nullable\": false, \"metadata\": {\"comment\": \"the second string\"}}', type_name=<ColumnTypeName.STRING: 'STRING'>, type_precision=None, type_scale=None, type_interval_type=None, position=1, parameter_mode=None, parameter_type=None, parameter_default=None, comment='the second string')]), data_type=<ColumnTypeName.STRING: 'STRING'>, full_data_type='STRING', return_params=None, routine_body='EXTERNAL', routine_definition='concatenated = f\"{a} {b}\"\\n\\n# Convert the concatenated string to uppercase\\nuppercased = concatenated.upper()\\n\\nreturn uppercased', routine_dependencies=None, parameter_style='S', is_deterministic=True, sql_data_access='NO_SQL', is_null_call=False, security_type='DEFINER', specific_name='my_test_func', comment='Returns an upper case concatenation of two strings separated by a space.', properties='null', full_name='AICatalog.AISchema.my_test_func', owner=None, created_at=1732302867741, created_by=None, updated_at=1732302867741, updated_by=None, function_id='dffc72ad-72ad-4000-8a5a-89389c93c45f', external_language='PYTHON')"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def my_test_func(a: str, b: str) -> str:\n",
    "    \"\"\"\n",
    "    Returns an upper case concatenation of two strings separated by a space.\n",
    "\n",
    "    Args:\n",
    "        a: the first string\n",
    "        b: the second string\n",
    "\n",
    "    Returns:\n",
    "        Uppercased concatenation of the two strings.\n",
    "    \"\"\"\n",
    "    # Concatenate the two strings with a space\n",
    "    concatenated = f\"{a} {b}\"\n",
    "\n",
    "    # Convert the concatenated string to uppercase\n",
    "    uppercased = concatenated.upper()\n",
    "\n",
    "    return uppercased\n",
    "\n",
    "\n",
    "my_callable_func = uc_client.create_python_function(\n",
    "    func=my_test_func,\n",
    "    catalog=CATALOG,\n",
    "    schema=SCHEMA,\n",
    "    replace=True,\n",
    ")\n",
    "\n",
    "my_callable_func"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b51a4bb-5fbe-46a1-a053-ee20607488ff",
   "metadata": {},
   "source": [
    "## Verify function execution\n",
    "\n",
    "Now that we have our function created within Unity Catalog, we can validate that the function is executable by using the `execute_function` API available within the `UnitycatalogFunctionClient` instance. \n",
    "\n",
    "> WARNING: Functions that are defined within Unity Catalog are executed in the **local environment that you are calling from**, within a subprocess. Be **very careful** when executing functions that you did not author and ensure that you understand the contents of the `route_definition` of any function that you are going to execute before doing so. You can inspect the contents of a function either by navigating to the Unity Catalog UI or by retrieving the `FunctionInfo` through the `get_function` API. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "2fb2e36e-4c6f-4302-a488-958f1885094b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "FunctionExecutionResult(error=None, format='SCALAR', value='HI THERE', truncated=None)"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "FUNC = f\"{CATALOG}.{SCHEMA}.my_test_func\"\n",
    "\n",
    "uc_client.execute_function(function_name=FUNC, parameters={\"a\": \"hi\", \"b\": \"there\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7bbbad07-7363-41c7-9e02-b2889278dc6e",
   "metadata": {},
   "source": [
    "## Create the functions for our Agent\n",
    "\n",
    "Now that we've seen how to create and verify the functionality of a simple test function, let's define some functions that we'll be creating for our OpenAI Agent. \n",
    "\n",
    "What this simple tool calling Agent will do is provide an interface for messaging a weather forecast "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "60c2ab5c-3a7a-4a01-bd20-5be20adc6450",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fahrenheit_to_celsius(fahrenheit: float) -> float:\n",
    "    \"\"\"\n",
    "    Converts temperature from Fahrenheit to Celsius.\n",
    "\n",
    "    Args:\n",
    "        fahrenheit (float): Temperature in degrees Fahrenheit.\n",
    "\n",
    "    Returns:\n",
    "        float: Temperature in degrees Celsius.\n",
    "    \"\"\"\n",
    "    return (fahrenheit - 32) * 5.0 / 9.0\n",
    "\n",
    "\n",
    "def calculate_humidex_temperature(temperature_c: float, humidity: float) -> float:\n",
    "    \"\"\"\n",
    "    Calculates the Humidex temperature based on the actual temperature in Celsius and relative humidity.\n",
    "    High temperatures with high humidity feel hotter, while low temperatures with low humidity feel colder.\n",
    "    This function uses the Humidex formula to compute the perceived temperature.\n",
    "\n",
    "    Args:\n",
    "        temperature_c (float): Actual temperature in degrees Celsius.\n",
    "        humidity (float): Relative humidity percentage (0-100).\n",
    "\n",
    "    Returns:\n",
    "        float: Real feel temperature in degrees Celsius.\n",
    "    \"\"\"\n",
    "    import math\n",
    "\n",
    "    if humidity < 0 or humidity > 100:\n",
    "        raise ValueError(\"Humidity must be between 0 and 100 percent.\")\n",
    "\n",
    "    # Calculate water vapor pressure (e) in millibars\n",
    "    e = 6.11 * math.exp(5417.7530 * ((1 / 273.16) - (1 / (temperature_c + 273.15))))\n",
    "\n",
    "    # Humidex formula\n",
    "    return temperature_c + 0.5555 * (e * humidity / 100 - 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2d1b0a6e-7dd1-46dd-81b9-89192897a1f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create a UC function for Fahrenheit to Celsius conversion\n",
    "func1 = uc_client.create_python_function(\n",
    "    func=fahrenheit_to_celsius,\n",
    "    catalog=CATALOG,\n",
    "    schema=SCHEMA,\n",
    "    replace=True,\n",
    ")\n",
    "\n",
    "# Create a UC function based on the Humidex callable\n",
    "func2 = uc_client.create_python_function(\n",
    "    func=calculate_humidex_temperature,\n",
    "    catalog=CATALOG,\n",
    "    schema=SCHEMA,\n",
    "    replace=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8252acbe-bf4c-4c29-9f9e-d4c80950bec0",
   "metadata": {},
   "source": [
    "### Verify function creation\n",
    "\n",
    "Now that we've created our Python functions `fahrenheit_to_celsius` and `calculate_humidex_temperature` within our schema that we've created, we can use the `UnitycatalogFunctionClient` to list all of the functions that are contained within a given schema. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f560078e-ab4b-4d36-bc3f-0e553a61c69d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['calculate_humidex_temperature', 'fahrenheit_to_celsius', 'my_test_func']"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "names = [info.name for info in uc_client.list_functions(catalog=CATALOG, schema=SCHEMA)]\n",
    "\n",
    "names"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "05eca317-604b-4bc8-a88b-264cc5ff88fe",
   "metadata": {},
   "source": [
    "## Run our functions\n",
    "\n",
    "When executing a function, there are two inputs required in the caller's interface:\n",
    "\n",
    "- **function_name**: The fully qualified name in the form `[catalog].[schema].[function name]`.\n",
    "    Since we used the `create_python_function` API, the *function name* is the name of our callable that we passed in.\n",
    "- **parameters**: A dictionary input (passed effectively as a `**kwargs` input to our function). The keys that are required for the parameters dictionary are the arguments to our function. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e0034d89-0f88-4ac1-81a7-45bdebfd20a3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "FunctionExecutionResult(error=None, format='SCALAR', value='31.27777777777778', truncated=None)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Run the functions to ensure that everything is working properly\n",
    "f_to_c = f\"{CATALOG}.{SCHEMA}.fahrenheit_to_celsius\"\n",
    "\n",
    "celsius = uc_client.execute_function(function_name=f_to_c, parameters={\"fahrenheit\": 88.3})\n",
    "\n",
    "celsius"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a1a1d079-55ef-4491-b293-01f140df2ad0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "FunctionExecutionResult(error=None, format='SCALAR', value='44.61870689130167', truncated=None)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "humidex_func = f\"{CATALOG}.{SCHEMA}.calculate_humidex_temperature\"\n",
    "\n",
    "humidex = uc_client.execute_function(\n",
    "    function_name=humidex_func, parameters={\"temperature_c\": float(celsius.value), \"humidity\": 72.6}\n",
    ")\n",
    "\n",
    "humidex"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61562488-1e84-4a90-a4d7-0086208feb28",
   "metadata": {},
   "source": [
    "## Build a tool calling Agent with OpenAI\n",
    "\n",
    "In order to let a GenAI service like a GPT model hosted by OpenAI use our functions, we need to register them as tools. \n",
    "\n",
    "To do this, we'll import the Unity Catalog AI OpenAI integration package and utilize the `UCFunctionToolkit` class to construct the interface we need to register tools. \n",
    "\n",
    "> Note: Each integration for UnityCatalog's AI libraries utilizes the same interface name `UCFunctionToolkit`. If you need to create a complex workflow that involves integrations between different services, ensure that you alias your imports for the toolkit constructors."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "49862c35-959e-468b-a560-242504d9ab5c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from unitycatalog.ai.openai.toolkit import UCFunctionToolkit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "65ba5a54-56b1-4fc7-aea0-671739300f80",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'type': 'function',\n",
       "  'function': {'name': 'AICatalog__AISchema__fahrenheit_to_celsius',\n",
       "   'strict': True,\n",
       "   'parameters': {'properties': {'fahrenheit': {'description': 'Temperature in degrees Fahrenheit.',\n",
       "      'title': 'Fahrenheit',\n",
       "      'type': 'number'}},\n",
       "    'title': 'AICatalog__AISchema__fahrenheit_to_celsius__params',\n",
       "    'type': 'object',\n",
       "    'additionalProperties': False,\n",
       "    'required': ['fahrenheit']},\n",
       "   'description': 'Converts temperature from Fahrenheit to Celsius.'}},\n",
       " {'type': 'function',\n",
       "  'function': {'name': 'AICatalog__AISchema__calculate_humidex_temperature',\n",
       "   'strict': True,\n",
       "   'parameters': {'properties': {'temperature_c': {'description': 'Actual temperature in degrees Celsius.',\n",
       "      'title': 'Temperature C',\n",
       "      'type': 'number'},\n",
       "     'humidity': {'description': 'Relative humidity percentage (0-100).',\n",
       "      'title': 'Humidity',\n",
       "      'type': 'number'}},\n",
       "    'title': 'AICatalog__AISchema__calculate_humidex_temperature__params',\n",
       "    'type': 'object',\n",
       "    'additionalProperties': False,\n",
       "    'required': ['temperature_c', 'humidity']},\n",
       "   'description': 'Calculates the Humidex temperature based on the actual temperature in Celsius and relative humidity. High temperatures with high humidity feel hotter, while low temperatures with low humidity feel colder. This function uses the Humidex formula to compute the perceived temperature.'}}]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Create the toolkit instance with the fully qualified function names that we defined earlier.\n",
    "toolkit = UCFunctionToolkit(client=uc_client, function_names=[func1.full_name, func2.full_name])\n",
    "\n",
    "# Extract the `tools` property of the toolkit so that the tool definitions can be passed to the OpenAI LLM.\n",
    "tools = toolkit.tools\n",
    "\n",
    "tools"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5da9e49e-9d57-4a1c-b396-6c414d9b1733",
   "metadata": {},
   "source": [
    "#### Verify that we can communicate with OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ba3970c4-8f97-4f58-ad89-ddeade75f919",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ensure that the OpenAI API Key is set within the environment\n",
    "import os\n",
    "\n",
    "assert os.environ.get(\"OPENAI_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c004bff8-6c0d-4dc4-a8ad-2ed5852bb1c1",
   "metadata": {},
   "source": [
    "## Interface with OpenAI's GPT model with tool calling capabilities\n",
    "\n",
    "In the following code block, we submit our messages to OpenAI. As with a standard query, we provide both a system prompt message and a user message. \n",
    "\n",
    "In order to allow the LLM to utilize tool calling capabilities, we pass our toolkit definitions (`toolkit.tools`) to the `tools` argument within the OpenAI SDK `chat.completions.create` API. With the tool definitions provided, OpenAI's LLM is available to contextually 'decide' when it is appropriate to request a tool call to be executed with its response. \n",
    "\n",
    "In the case of the question that we're providing, OpenAI will recognize that it should call both of our tools in order to facilitate an accurate answer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "bfdd0480-70a2-4477-97b7-f34ff0e3f60c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatCompletion(id='chatcmpl-AWTOmZx4lJvMMnegSC9J4DKh7SCyQ', choices=[Choice(finish_reason='tool_calls', index=0, logprobs=None, message=ChatCompletionMessage(content=None, refusal=None, role='assistant', audio=None, function_call=None, tool_calls=[ChatCompletionMessageToolCall(id='call_8Fi4ufZL2sA2zC5fr5Af7EMO', function=Function(arguments='{\"fahrenheit\": 97.3}', name='AICatalog__AISchema__fahrenheit_to_celsius'), type='function'), ChatCompletionMessageToolCall(id='call_GxRGWyzCtTIXnzEp8MMRGx6w', function=Function(arguments='{\"temperature_c\": 36.333333333333336, \"humidity\": 80.6}', name='AICatalog__AISchema__calculate_humidex_temperature'), type='function')]))], created=1732302868, model='gpt-4o-mini-2024-07-18', object='chat.completion', service_tier=None, system_fingerprint='fp_0705bf87c0', usage=CompletionUsage(completion_tokens=81, prompt_tokens=270, total_tokens=351, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)))"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import openai\n",
    "\n",
    "initial_messages = [\n",
    "    {\n",
    "        \"role\": \"system\",\n",
    "        \"content\": \"You are a helpful assistant that is designed to provide recommendations to help keep users comforable and \"\n",
    "        \"safe with respect to weather forecast questions. Please suggestions on what to wear, what additional context a user \"\n",
    "        \"might need for outdoor adventures.\",\n",
    "    },\n",
    "    {\n",
    "        \"role\": \"user\",\n",
    "        \"content\": \"The forecast tomorrow is 97.3F with 80.6% humidity. Should I go on a hike in the mountains?\",\n",
    "    },\n",
    "]\n",
    "\n",
    "response = openai.chat.completions.create(\n",
    "    model=\"gpt-4o-mini\",\n",
    "    messages=initial_messages,\n",
    "    tools=tools,\n",
    ")\n",
    "\n",
    "# The `finish_reason` for this response is `tool_calls` indicating that the LLM is requesting the response from executing the tools we\n",
    "# defined for that purpose.\n",
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42a599cb-0667-4a65-a8e5-a17bf6cf8137",
   "metadata": {},
   "source": [
    "### Generate the tool call messages\n",
    "\n",
    "With the response from OpenAI, we can then use a utility function in `unitycatalog-openai` to help format, call our function, and create the appropriate response format for the response call back to OpenAI. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "eaa00e4c-f487-464e-b802-a886f8b21d77",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'content': None,\n",
       "  'refusal': None,\n",
       "  'role': 'assistant',\n",
       "  'tool_calls': [{'id': 'call_8Fi4ufZL2sA2zC5fr5Af7EMO',\n",
       "    'function': {'arguments': '{\"fahrenheit\": 97.3}',\n",
       "     'name': 'AICatalog__AISchema__fahrenheit_to_celsius'},\n",
       "    'type': 'function'},\n",
       "   {'id': 'call_GxRGWyzCtTIXnzEp8MMRGx6w',\n",
       "    'function': {'arguments': '{\"temperature_c\": 36.333333333333336, \"humidity\": 80.6}',\n",
       "     'name': 'AICatalog__AISchema__calculate_humidex_temperature'},\n",
       "    'type': 'function'}]},\n",
       " {'role': 'tool',\n",
       "  'content': '{\"content\": \"36.27777777777778\"}',\n",
       "  'tool_call_id': 'call_8Fi4ufZL2sA2zC5fr5Af7EMO'},\n",
       " {'role': 'tool',\n",
       "  'content': '{\"content\": \"58.83411235106172\"}',\n",
       "  'tool_call_id': 'call_GxRGWyzCtTIXnzEp8MMRGx6w'}]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from unitycatalog.ai.openai.utils import generate_tool_call_messages\n",
    "\n",
    "messages = generate_tool_call_messages(response=response, client=uc_client)\n",
    "\n",
    "messages"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a61dff51-beab-4ba1-981c-9396ab18259f",
   "metadata": {},
   "source": [
    "### Pass the results back to OpenAI\n",
    "\n",
    "Now that we have the results from our tool call executions (both of them), generated in the format needed to make the next response call, we can submit this payload back to OpenAI so that the LLM can respond with their final recommendations! \n",
    "\n",
    "> NOTE: In order for OpenAI's LLM services to have the full context of the conversation history (the APIs are stateless), the history of the session's messages should be prepended to each additional message (as shown below with `final_messages = initial_messages + messages`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "10a50405-6304-46ea-8d9b-97fd643b7e06",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "ChatCompletion(id='chatcmpl-AWTOoKvQA9AIRfrKjBuoM5ypbheU7', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content=\"The temperature is approximately 36.3°C (97.3°F), and with a high humidity of 80.6%, the humidex (perceived temperature) feels like about 58.8°C (137.8°F). This is extremely high and can be dangerous for outdoor activities like hiking.\\n\\n**Recommendations:**\\n- **Clothing:** Wear lightweight, breathable, and moisture-wicking clothing to help with sweat evaporation. A wide-brimmed hat and sunglasses are also important for sun protection.\\n- **Hydration:** Carry plenty of water to stay hydrated. The heat and humidity can lead to dehydration quickly.\\n- **Timing:** If you decide to hike, consider going very early in the morning or later in the evening when temperatures are cooler.\\n- **Pace:** Take breaks often, and listen to your body. If you feel dizzy, weak, or excessively tired, it's best to stop and seek shade or a cool environment.\\n- **Safety:** Be aware of the signs of heat exhaustion and heat stroke.\\n\\nGiven the extreme weather conditions, it may be safer to postpone your hike for a cooler day if possible.\", refusal=None, role='assistant', audio=None, function_call=None, tool_calls=None))], created=1732302870, model='gpt-4o-mini-2024-07-18', object='chat.completion', service_tier=None, system_fingerprint='fp_0705bf87c0', usage=CompletionUsage(completion_tokens=228, prompt_tokens=387, total_tokens=615, completion_tokens_details=CompletionTokensDetails(accepted_prediction_tokens=0, audio_tokens=0, reasoning_tokens=0, rejected_prediction_tokens=0), prompt_tokens_details=PromptTokensDetails(audio_tokens=0, cached_tokens=0)))"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Prepend the message history\n",
    "final_messages = initial_messages + messages\n",
    "\n",
    "response = openai.chat.completions.create(\n",
    "    model=\"gpt-4o-mini\",\n",
    "    messages=final_messages,\n",
    "    tools=tools,\n",
    ")\n",
    "\n",
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "0f21b701-919a-4727-b77b-d0f6260e15eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"The temperature is approximately 36.3°C (97.3°F), and with a high humidity of 80.6%, the humidex (perceived temperature) feels like about 58.8°C (137.8°F). This is extremely high and can be dangerous for outdoor activities like hiking.\\n\\n**Recommendations:**\\n- **Clothing:** Wear lightweight, breathable, and moisture-wicking clothing to help with sweat evaporation. A wide-brimmed hat and sunglasses are also important for sun protection.\\n- **Hydration:** Carry plenty of water to stay hydrated. The heat and humidity can lead to dehydration quickly.\\n- **Timing:** If you decide to hike, consider going very early in the morning or later in the evening when temperatures are cooler.\\n- **Pace:** Take breaks often, and listen to your body. If you feel dizzy, weak, or excessively tired, it's best to stop and seek shade or a cool environment.\\n- **Safety:** Be aware of the signs of heat exhaustion and heat stroke.\\n\\nGiven the extreme weather conditions, it may be safer to postpone your hike for a cooler day if possible.\""
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response.choices[0].message.content"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "mlflow-311",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
